#include "OP_CreateTorus.h"
#include "../Geometries/PolygonMesh.h"

using namespace std;
using namespace CICore;
using namespace CIGeom;
using namespace CIView;

///<************************************************************************************************************>
///<************************************* region CONSTANTS DEFINITION ******************************************>
///<************************************************************************************************************>

/***************************************************************************************************************
************************************** endregion CONSTANTS DEFINITION ******************************************
****************************************************************************************************************/












/// <summary> 
/// Constructor 
/// </summary>
/// <param name="v"> A reference to the vector that contains the four 3D views pointers </param>
CIOps::OP_CreateTorus::OP_CreateTorus (vector<CIView::View*>& v)
: Operation (v), torus (0), radius (0.0006f), thickness (OperationManager::Thickness/10000.0f), 
  torusDetail (1), rotation_x (0), rotation_y (0) {

    newSelectionMode = scene.GetSelectionMode();

    stacks = OperationManager::Stacks;
    slices = OperationManager::Slices;
}

/// <summary> 
/// Destructor 
/// </summary>
CIOps::OP_CreateTorus::~OP_CreateTorus() {
    if (status != COMPLETED)
        delete torus;
}










///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary> 
/// Method called to execute directly the operation 
/// </summary>
/// <returns> The operation status </returns>
int CIOps::OP_CreateTorus::Exec() {
    switch (status) {
        case INITIAL:
            status = WAITING_FOR_EVENTS;
            break;
    }
    return status;        
}

/// <summary> 
/// Method called to undo the operation 
/// </summary>
void CIOps::OP_CreateTorus::Undo() {
    status = UNDO;      

    scene.RemoveGeometry (torus->GetName().c_str());    //Removes the geometry from the 3D scene
    RestoreOldSelection();                              //Restores the old selection status
    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Method called to redo the operation 
/// </summary>
void CIOps::OP_CreateTorus::Redo() {
    status = COMPLETED;    

    scene.AddGeometry (torus);                      //Re-adds the geometry in the 3D scene  
    RestoreNewSelection();                          //Restore the new selectio status
    UpdateViews();                                  //Updates the 3D views
}

/// <summary> 
/// Mouse Pressed event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse press event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Pressed event </param>
void CIOps::OP_CreateTorus::MousePressEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();
    
    SaveOldSelection();                             //Saves the old selection status
    scene.DeselectAllFaces();                       //Deselects all faces       //TODO -> Deselect all (included geometries and groups)

    view->WindowToWorldCoordinates (&x, &y);        //Unproject the mouse cursor coordinates

    Orientation& cameraOrient = view->GetCameraOrientation();

    //Computing the torus topLeft according to the current camera of the view that has thrown the event
    switch (view->GetCameraType()) {
        case Camera::TOP:
            topLeft.x = x;
            topLeft.z = y;
            rotation_x = 90.0f;
            break;
        
        case Camera::FRONT:
            topLeft.x = x;
            topLeft.y = -y;
            break;
        
        case Camera::RIGHT:            
            topLeft.y = -y;
            topLeft.z = -x;
            rotation_y = -90.0f;
            break;
    }

    unsigned int id = scene.GenerateNewGeometryID();        //Gets a reference to the number of geometries that have been added to the scene
    stringstream name;
    name << "Torus" << id;                                  //Sets the name of the new triangle mesh 

    torus = new PolygonMesh (name.str());                   //Creates the torus 

    CreateTorusWithTriangles (stacks*torusDetail, 
                              slices*torusDetail, 
                              thickness, 
                              radius);                  //Creates the torus with triangles as faces

    scene.AddGeometry (torus);                              //Adds the torus in the 3D scene

    UpdateViews();                                          //Updates the 3D views
}

/// <summary> 
/// Mouse Released event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse release event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Released event </param>
void CIOps::OP_CreateTorus::MouseReleaseEvent (CIView::View* view, QMouseEvent* e) {
    status = COMPLETED;

    view->SetPrimitivesSelected (true);                     //Signals that there is a primitive selected

    SaveNewSelection();                     //Saves the new selection status
    torus->UpdateBoundingBox();             //Updates the torus bounding box
}

/// <summary> 
/// Mouse Moved event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse move event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Mouse Moved event </param>
void CIOps::OP_CreateTorus::MouseMoveEvent (CIView::View* view, QMouseEvent* e) {
    float x = e->x();
    float y = e->y();

    view->WindowToWorldCoordinates (&x, &y);            //Unproject the mouse cursor coordinates

    Orientation& cameraOrient = view->GetCameraOrientation();

    Vector3D drag;    
    float newRadius, scaleRadius; 
    float newHeight, scaleHeight;
    Matrix scaling;

    //Computing the new radius value according to the mouse drag values
    switch (view->GetCameraType()) {
        case Camera::TOP:
            drag.x = x - topLeft.x;
            drag.z = y - topLeft.z;
            newRadius = drag.x ? drag.x / 2.0f : radius;
            newHeight = drag.z ? drag.z / 4.0f : thickness;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / thickness;

            scaling.SetScaling (scaleRadius, scaleRadius, scaleHeight);            
            break;
        
        case Camera::FRONT:
            drag.x = x - topLeft.x;
            drag.y = -topLeft.y - y;
            newRadius = drag.x ? drag.x / 2.0f : radius;
            newHeight = drag.y ? drag.y / 4.0f : thickness;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / thickness;

            scaling.SetScaling (scaleRadius, scaleHeight, scaleRadius);         
            break;
        
        case Camera::RIGHT:         
            drag.z = -topLeft.z - x;
            drag.y = -topLeft.y - y;            
            newRadius = drag.z ? drag.z / 2.0f : radius;
            newHeight = drag.y ? drag.y / 4.0f : thickness;

            scaleRadius = newRadius / radius;
            scaleHeight = newHeight / thickness;

            scaling.SetScaling (scaleRadius, scaleHeight, scaleRadius);         
            break;
    }    

    thickness = newHeight;
    radius = newRadius;

    //Scaling the torus according to the new radius and thickness
    Matrix m1, m2;
    m1.SetTranslation (-topLeft.x, -topLeft.y, -topLeft.z);
    m2.SetTranslation (topLeft.x, topLeft.y, topLeft.z);
    
    Matrix m;
    m = m1*scaling*m2;
    torus->ApplyMatrix (m);

    UpdateViews();                                      //Updates the 3D views
}

/// <summary> 
/// Wheel event handler, called whenever the operation 
/// is in execution and a 3D view intercepts a mouse wheel event
/// </summary>
/// <param name="view"> The view that has thrown the event </param>
/// <param name="e"> Wheel event </param>
void CIOps::OP_CreateTorus::WheelEvent (CIView::View* view, QWheelEvent* e) {
    if ((e->delta() > 0 && torusDetail == 4) || (e->delta() <= 1 && torusDetail == 1))
        return;

    e->delta() > 0 ? torusDetail++ : torusDetail--;     //Increases/Decreases the torus triangle tessellation detail
    
    if (stacks*torusDetail > 30 || slices*torusDetail > 30)
        return;

    torus->DeleteAllVertices();                         //Deletes the old torus vertices

    CreateTorusWithTriangles (stacks*torusDetail, 
                              slices*torusDetail, 
                              thickness, 
                              radius);              //Creates the torus with triangles as faces

    UpdateViews();                                      //Updates the 3D views
}
/***************************************************************************************************************
***************************************** endregion Utility Methods ********************************************
****************************************************************************************************************/









///<***********************************************************************************************************>
///<*********************************** region Triangles Tessellation *****************************************>
///<***********************************************************************************************************>

/// <summary> 
/// Method called to create a torus centered at the origin 
/// </summary>
/// <param name="stacks"> The number of stacks (for the torus subdivision in faces) </param>
/// <param name="slices"> The number of slices (for the torus subdivision in faces) </param>
/// <param name="thickness"> The torus thickness </param>
/// <param name="radius"> The torus radius </param>
void CIOps::OP_CreateTorus::CreateTorusWithTriangles (int stacks, int slices, float thickness, float radius) {
    double stackAngle = 2.0 * PI / stacks;
    double sliceAngle = 2.0 * PI / slices;

    int i, j, v1_index, v2_index, v3_index;

    double a0 = 0;
    double a1 = stackAngle;
    float x0 = cos(a0);
    float y0 = sin(a0);
    float x1 = cos(a1);
    float y1 = sin(a1);

    double b = 0;
    float c = cos(b);
    float r = thickness * c + radius;
    float z = thickness * sin(b);
                                                
    v1_index = torus->AddVertex (x0 * r, y0 * r, z);                      
    v2_index = torus->AddVertex (x1 * r, y1 * r, z);           

    for (i=0; i<stacks; i++) {
        a0 = i * stackAngle;
        a1 = a0 + stackAngle;
        x0 = cos(a0);
        y0 = sin(a0);
        x1 = cos(a1);
        y1 = sin(a1);
        
        j = i ? 0 : 1;

        for (; j<=slices; j++) {
            b = j * sliceAngle;
            c = cos(b);
            r = thickness * c + radius;
            z = thickness * sin(b);

            //Adding a face
            v3_index = torus->AddVertex (x0 * r, y0 * r, z);  
            AddFace (v1_index, v2_index, v3_index);
            v1_index = v3_index;

            //Adding another one 
            v3_index = torus->AddVertex (x1 * r, y1 * r, z);  
            AddFace (v1_index, v2_index, v3_index);
            v2_index = v3_index;
        }
    }

    //Translating the torus vertices according to the position desidered
    Point torusCentre;                

    if (rotation_x == 0) {
         torusCentre.x = topLeft.x+radius;
         torusCentre.y = topLeft.y+thickness;
         torusCentre.z = topLeft.z+radius;
    }
    else {
         torusCentre.x = topLeft.x+radius;
         torusCentre.y = topLeft.y+radius;
         torusCentre.z = topLeft.z+thickness;
    }

    Matrix m1, m2, m3, m;
    m1.SetRotation (rotation_x, 0, 0);
    m2.SetRotation (0, rotation_y, 0);
    m3.SetTranslation (torusCentre.x, torusCentre.y, torusCentre.z);
    m = m1 * m2 * m3;
    torus->ApplyMatrix (m);
}

/// <summary> 
/// Method called to add a face (triangle) to the torus
/// </summary>
/// <param name="v1"> The index of the first vertex int the torus vertex library </param>
/// <param name="v2"> The index of the second vertex int the torus vertex library </param>
/// <param name="v3"> The index of the third vertex int the torus vertex library </param>
void CIOps::OP_CreateTorus::AddFace (int v1, int v2, int v3) {
    if (v1 == v2 || v1 == v3 || v2 == v3)
        return;

    VertexLib vertices(3);
    VertexLib& torusVertices = torus->GetVertices();
    Face* newFace;
    
    if (rotation_x != 0) {
        vertices[0] = torusVertices[v1];
        vertices[1] = torusVertices[v2];
        vertices[2] = torusVertices[v3];
    }
    else {
        vertices[0] = torusVertices[v2];
        vertices[1] = torusVertices[v1];
        vertices[2] = torusVertices[v3];
    }

    newFace = new Face (vertices);
    newFace->ComputeNormal();
    torus->AddFace (newFace);
}
/***************************************************************************************************************
*********************************** endregion Triangles Tessellation *******************************************
****************************************************************************************************************/

